package com.kmx.demo.utils;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

public class Request implements HttpConstants{

	private static Logger logger = Logger.getLogger(Request.class);

	private RequestMethod requestMethod;
	private String url;
	private String body;
	private Map<String, String> headers = new HashMap<>();
	private Map<String, String> cookies = new HashMap<>();
	private Map<String, String> queryParams = new HashMap<>();
	private Map<String, String> formParams = new HashMap<>();
	private HttpEntity entity;
	private HttpClientContext localContext = HttpClientContext.create();;
	private CloseableHttpClient httpClient = HttpClients.createDefault();
	private CloseableHttpResponse httpResponse = null;

	public Request(RequestMethod requestMethod, String url) {
		this.method(requestMethod);
		this.url(url);
	}

	public Request(RequestMethod requestMethod, String url,Object body) {
		this.method(requestMethod);
		this.url(url);
		this.body(body);
	}
	
	public Request(String key, String value) {
		this.headers.put(key, value);
	}
	
	public static Request get(String url) {
		return new Request(RequestMethod.GET, url);
	}

	public static Request post(String url) {
		return new Request(RequestMethod.POST, url);
	}

	public static Request put(String url) {
		return new Request(RequestMethod.PUT, url);
	}

	public static Request delete(String url) {
		return new Request(RequestMethod.DELETE, url);
	}
	
	public Request url(String url) {
		this.url = url;
		return this;
	}

	public Request body(Object body) {
		if (body != null) {
			this.body = body.toString();
		}
		return this;
	}

	public Request headers(Map<String, String> headers) {
		this.headers.putAll(headers);
		return this;
	}

	public Request headers(String key, String value) {
		this.headers.put(key, value);
		return this;
	}
	
	public Request method(RequestMethod requestMethod) {
		this.requestMethod = requestMethod;
		return this;
	}

	public Request cookies(Map<String, String> cookies) {
		this.cookies = cookies;
		setCookies(cookies);
		return this;
	}

	public Request cookies(String key, String value) {
		this.cookies.put(key, value);
		return this;
	}

	public Request queryParams(Map<String, String> queryParams) {
		this.queryParams = queryParams;
		return this;
	}

	public Request queryParams(String key, String value) {
		this.queryParams.put(key, value);
		return this;
	}

	public Request formParams(Map<String, String> formParams) {
		this.formParams = formParams;
		return this;
	}

	public Request formParams(String key, String value) {
		this.formParams.put(key, value);
		return this;
	}
	
	public String getUrl(){
		return this.url;
	}
	
	public String getBody(){
		return this.body;
	}
	
	public CloseableHttpResponse getCloseableHttpResponse(){
		return this.httpResponse;
	}
	
	public RequestMethod getRequestMethod(){
		return this.requestMethod;
	}
	
	public Map<String, String> getRequestHeaders(){
		return this.headers;
	}

	public Request run() throws URISyntaxException, ClientProtocolException, IOException {
		String url = this.url;
		HttpRequestBase method = null;
		// Build Entity
		if (this.body != null) {
			this.entity = new StringEntity(this.body.toString(), CHARSET);
		}
		logger.debug("Payload:  " + body);

		// Handle query parameters
		if (queryParams.size() > 0) {
			url += "?";
			String params = "";
			for (Map.Entry<String, String> entry : queryParams.entrySet()) {
				if (params.length() > 0) {
					params += "&";
				}
				params += entry.getKey() + "=" + entry.getValue();
			}
			url += params;
		}
		URI uri = buildURI(url);

		switch (requestMethod) {
			case GET :
				if (url.toString().contains("+")) {
					method = new HttpGet(new URL(uri.toString().replaceAll("%25", "%").replaceAll("\\+", "%2B")).toString());
				} else {
					method = new HttpGet(uri);
				}
				break;
			case POST :
				method = new HttpPost(uri);
				if (formParams.size() > 0) {
					List<NameValuePair> formparams = new ArrayList<NameValuePair>();
					for (Map.Entry<String, String> entry : formParams.entrySet()) {
						formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
					}
					entity = new UrlEncodedFormEntity(formparams, CHARSET);
				}
				if (entity != null) {
					logger.debug("Entity:   " + entity.toString());
					((HttpPost) method).setEntity(entity);
				}
				break;
			case PUT :
				method = new HttpPut(uri);
				if (formParams.size() > 0) {
					List<NameValuePair> formparams = new ArrayList<NameValuePair>();
					for (Map.Entry<String, String> entry : formParams.entrySet()) {
						formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
					}
					entity = new UrlEncodedFormEntity(formparams, CHARSET);
				}
				if (entity != null) {
					logger.debug("Entity:   " + entity.toString());
					((HttpPut) method).setEntity(entity);
				}
				break;
			case DELETE :
				method = new HttpDelete(uri);
				break;
			default :
				logger.error("The Http Method <" + this.requestMethod + "> is not supported yet!");
				return null;
		}
		
		setHeader(method);
		logger.debug(method.toString());
		this.httpResponse = httpClient.execute(method, localContext);
		return this;
	}

	public String statusLine() throws IOException {
		String line = "";
		try {
			line = httpResponse.getStatusLine().toString();
		} finally {
			httpResponse.close();
		}
		return line;
	}

	public HashMap<String, Object> getHttpResponse() {

		HashMap<String, Object> res = new HashMap<String, Object>();
		try{
		res.put("statusCode", httpResponse.getStatusLine().getStatusCode());
		res.put("reasonPhrase", httpResponse.getStatusLine().getReasonPhrase());
		res.put("protocolVersion", httpResponse.getStatusLine().getProtocolVersion());
		res.put("locale", httpResponse.getLocale());
		res.put("allHeaders", httpResponse.getAllHeaders());
		}finally{
			try {
				httpResponse.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return res;
	}

	public int statusCode() throws IOException {
		int code = -999;
		try {
			code = httpResponse.getStatusLine().getStatusCode();
		} finally {
			httpResponse.close();
		}
		return code;
	}

	public String reasonPhrase() throws IOException {
		String rp = "";
		try {
			rp = httpResponse.getStatusLine().getReasonPhrase();
		} finally {
			httpResponse.close();
		}
		return rp;
	}

	public String getHeader(String headerName) throws IOException {
		String value = "";
		try {
			value = httpResponse.getFirstHeader(headerName).getValue();
		} finally {
			httpResponse.close();
		}
		return value;
	}

	public String protocolVersion() throws IOException {
		String pv = "";
		try {
			pv = httpResponse.getStatusLine().getProtocolVersion().toString();
		} finally {
			httpResponse.close();
		}
		return pv;
	}

	public static String getResponsePayload(CloseableHttpResponse httpResponse) throws IOException {
		String payload = null;
		HttpEntity entity = httpResponse.getEntity();
		try {
			payload = EntityUtils.toString(entity, CHARSET);
			EntityUtils.consume(entity);
		} catch (ParseException e) {
			e.printStackTrace();
		} finally {
			httpResponse.close();
		}
		return payload;
	}
	
	public String responsePayload() throws IOException {
		String payload = null;
		entity = httpResponse.getEntity();
		try {
			payload = EntityUtils.toString(entity, CHARSET);
			EntityUtils.consume(entity);
		} catch (ParseException e) {
			e.printStackTrace();
		} finally {
			httpResponse.close();
		}
		return payload;
	}

	public void close() {
		if (httpResponse != null) {
			try {
				httpResponse.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if (this.httpClient != null) {
			try {
				httpClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private URI buildURI(String URL) throws MalformedURLException, URISyntaxException {
		URL url = new URL(URL);
		return new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(),url.getQuery(), null);
	}

	private void setCookies(Map<String, String> cookies) {
		this.cookies = cookies;
		// Create a local instance of cookie store
		CookieStore cookieStore = new BasicCookieStore();

		for (Map.Entry<String, String> entry : cookies.entrySet()) {
			BasicClientCookie mycookie = new BasicClientCookie(entry.getKey(), entry.getValue());
			cookieStore.addCookie(mycookie);
		}
		localContext.setCookieStore(cookieStore);
	}

	private void setHeader(HttpRequestBase method) {
		if (headers.size() > 0) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				method.setHeader(entry.getKey(), entry.getValue());
			}
		}
	}
	
	public static void main(String[] args) throws ClientProtocolException, URISyntaxException, IOException {
		for (int i = 0; i < 200; i++) {
			Request r = Request.get("http://192.168.130.2/cloud/qa1/kmx/v2/field-groups").run();
			System.out.println(r.statusCode());
			System.out.println(r.statusLine());
			System.out.println(r.responsePayload());
			System.out.println();
		}
	}
}
